/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/phone/VolumeManager.cpp,v 1.11 2003/11/21 16:12:58 itsme Exp $
 *
 *
 *  this module modifies the device volume, depending on the current call state
 *
 *  possible problem: the analog phoneapp, attempts might do the same.
 *
 *  a timer is used to notice that ringing has stopped, if more than 5 seconds no ring has been received.
 *  also a timer is used to keep the talk volume, until 10 seconds after disconnect, this is to avoid
 *  the problem that sometimes the volume goes back to pda-volume when still some 'call-disconnect' tone
 *  is playing, giving the user a really loud end-of-call experience.
 * 
 */
#include "CPhone.h"

#include <windows.h>

#include "nhRegistry.h"

#include "VolumeManager.h"

#include "CPhoneDlg.h"
#include "SpipcHandler.h"
#include "debug.h"

#include "stringutils.h"
#include <string>

inline DWORD BIT(int a) { return(1<<a); }
inline DWORD BITS(int a) { return BIT(a); }
inline DWORD BITS(int a, int b) { return BIT(a)|BIT(b); }
inline DWORD BITS(int a, int b, int c) { return BIT(a)|BIT(b)|BIT(c); }
inline DWORD BITS(int a, int b, int c, int d) { return BIT(a)|BIT(b)|BIT(c)|BIT(d); }
inline DWORD BITS(int a, int b, int c, int d, int e) { return BIT(a)|BIT(b)|BIT(c)|BIT(d)|BIT(e); }
inline DWORD BITS(int a, int b, int c, int d, int e, int f) { return BIT(a)|BIT(b)|BIT(c)|BIT(d)|BIT(e)|BIT(f); }



#define MAX_WAIT_EVENTS 8
#define EVENTMASK BITS

inline void SETBITS(DWORD& f, int a) { f |= BITS(a); }
inline void SETBITS(DWORD& f, int a, int b) { f |= BITS(a,b); }
inline void SETBITS(DWORD& f, int a, int b, int c) { f |= BITS(a,b,c); }

inline bool TESTALL(DWORD f, int a) { return (f&BITS(a))==BITS(a); }
inline bool TESTALL(DWORD f, int a, int b) { return (f&BITS(a,b))==BITS(a,b); }
inline bool TESTALL(DWORD f, int a, int b, int c) { return (f&BITS(a,b,c))==BITS(a,b,c); }

static std::string eventname(int n)
{
    switch(n)
    {
    case EV_VMGR_RING:          return "EV_VMGR_RING";
    case EV_VMGR_TIMER:          return "EV_VMGR_TIMER";
    case EV_VMGR_VOICECONNECT:  return "EV_VMGR_VOICECONNECT";
    case EV_VMGR_PHONEDISCONNECT:    return "EV_VMGR_PHONEDISCONNECT";
    case EV_VMGR_RILDISCONNECT:    return "EV_VMGR_RILDISCONNECT";
    case EV_VOLUME_UPDATE:      return "EV_VOLUME_UPDATE";
    default:
        return stringformat("EVENT-%d-UNKNOWN", n);
    }
}

// todo: extract the 'WaitState' stuff to a 'StateMachine' class
int VolumeManager::WaitState(DWORD eventMask)
{
    HANDLE hWaitEvents[MAX_WAIT_EVENTS];
    int nEventIds[MAX_WAIT_EVENTS];

    m_dwCurrentWait= eventMask;

    debug("VM: waiting for");

    // convert eventmask to list of event handles
    DWORD j=0;
    for (int i=0 ; i<32 ; i++)
    {
        if (m_dwCurrentWait&(1<<i))  {
            if (j<MAX_WAIT_EVENTS)
            {
                debug(" %d=%hs", j, eventname(i).c_str());

                nEventIds[j]= i;
                hWaitEvents[j]= m_hEvents[i];

                j++;
            }
            else
                debug("VM-ERROR - too many events in wait\n");
        }
    }
    debug("\n");

#if 0
//  ---- write current state of all events
    for (i=0 ; i<j ; i++)
    {
        int rs= WaitForSingleObject(hWaitEvents[i], 0);
        debug(" %d=(%d, %s)", i, rs, eventname(nEventIds[i]).c_str());
    }
    debug("\n");
#endif
    DWORD res= WaitForMultipleObjects(j, hWaitEvents, false, INFINITE);
    if (res>=WAIT_OBJECT_0 && res<WAIT_OBJECT_0+j)
    {
        debug("VM: event %d=%hs\n", res-WAIT_OBJECT_0, eventname(nEventIds[res-WAIT_OBJECT_0]).c_str());

        ResetEvent(hWaitEvents[res-WAIT_OBJECT_0]);

        return nEventIds[res-WAIT_OBJECT_0];
    }

    if (res>=WAIT_ABANDONED_0  && res<WAIT_ABANDONED_0 +j)
    {
        debug("VM-WaitForMultipleObjects ERROR %hs abandoned wait", eventname(nEventIds[res-WAIT_ABANDONED_0]).c_str());
        return -1;
    }
    if (res==WAIT_TIMEOUT)
        debug("VM-WaitForMultipleObjects ERROR timeout\n");
    else if (res==WAIT_FAILED)
        error("VM-WaitForMultipleObjects failed ");
    else
        error("VM-WaitForMultipleObjects error ");

    return -1;
}




VolumeManager::VolumeManager()
{
    // create events, manual reset, initially nonsignalled
    for (int i=0 ; i<VOLUME_NR_OF_EVENTS ; i++)
        m_hEvents[i]= CreateEvent(0, TRUE, FALSE, 0);

    m_dwCurrentWait= 0;
    m_dwCurrentVolume= 0;
    m_nVolumeOffset= 0;

    InitializeCriticalSection(&m_lock);
}
VolumeManager::~VolumeManager()
{
    DeleteCriticalSection(&m_lock);

    for (int i=0 ; i<VOLUME_NR_OF_EVENTS ; i++)
    {
        CloseHandle(m_hEvents[i]);
        m_hEvents[i]= NULL;
    }
}
void VolumeManager::StartThread()
{
    debug("starting VolumeManager thread\n");
    NHThread::StartThread();
    debug("VolumeManager thread started\n");
}

DWORD VolumeManager::ThreadProc()
{
    mainloop();

    return 0;
}

enum {
    STATE_IDLE,     // pda volume is active
    STATE_PREIDLE,  // just after hangup, playing hangup tone
    STATE_RINGING,  // alerting user
    STATE_TALKING   // phone is active ( either data, or voice call )
};

void VolumeManager::mainloop()
{
    m_state= STATE_IDLE;

    while (true)
    {
        LoadStateVolume(m_state);

        int n= WaitState(EVENTMASK(EV_VMGR_RING, EV_VMGR_TIMER, EV_VMGR_VOICECONNECT, 
                    EV_VMGR_RILDISCONNECT, EV_VMGR_PHONEDISCONNECT));
        if (n==-1)
            break;  // error

        int oldstate= m_state;

        if (!UpdateState(n))
            break;

        if (!SaveStateVolume(oldstate))
            break;
    }
}

int VolumeManager::GetStateMinimum()
{
    switch(m_state)
    {
    case STATE_IDLE:    return 0x0000;
    case STATE_PREIDLE: return 0x1800;
    case STATE_RINGING: return 0x0000;
    case STATE_TALKING: return 0x1800;
    default:            return 0;
    }
    

}

int VolumeManager::GetStateMaximum()
{
    switch(m_state)
    {
    case STATE_IDLE:    return 0xfc00;
    case STATE_PREIDLE: return 0x8000;
    case STATE_RINGING: return 0xfc00;
    case STATE_TALKING: return 0x8000;
    default:            return 0xfc00;
    }
}

// this method is called from the spipc handler, processing
// the set volume messages
void VolumeManager::UpdateCurrentVolume(DWORD volume)
{
    bool bDoUpdateVolume= false;

    EnterCriticalSection(&m_lock);
    m_dwCurrentVolume= volume;

    // see if volume needs to be changed.
    if (m_nVolumeOffset) {
        int newvolume= m_dwCurrentVolume + (m_nVolumeOffset<<13);
        if (newvolume < GetStateMinimum())
            newvolume= GetStateMinimum();
        if (newvolume > GetStateMaximum())
            newvolume= GetStateMaximum();
        m_nVolumeOffset= 0;

        m_dwCurrentVolume= newvolume;

        bDoUpdateVolume= true;
    }
    LeaveCriticalSection(&m_lock);

    if (bDoUpdateVolume)
        theApp.m_ipc->SetVolume(m_dwCurrentVolume);

    SetEvent(m_hEvents[EV_VOLUME_UPDATE]);
}

bool VolumeManager::UpdateState(int event)
{
    switch(event)
    {
    case EV_VMGR_RING: m_state= STATE_RINGING; return true;
    case EV_VMGR_TIMER: 
        // go to idle, either after 'ring'
        //  or 10 seconds after ril-disconnect
        if (m_state!=STATE_PREIDLE || ++m_nTimerCount >= 2)
            m_state= STATE_IDLE; 
        return true;
    case EV_VMGR_VOICECONNECT: m_state= STATE_TALKING; return true;
    case EV_VMGR_PHONEDISCONNECT: m_state= STATE_IDLE; return true;

        // pre idle turns into idle, after a timer (ringoff) event
    case EV_VMGR_RILDISCONNECT: m_state= STATE_PREIDLE; return true;
    case EV_VOLUME_UPDATE: return true;
    default:
        debug("unexpected event  %hs\n", eventname(event).c_str());
         return false;
    }
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
std::string GetStateString(int state)
{
    switch(state)
    {
    case STATE_IDLE:    return "Idle"; break;
    case STATE_PREIDLE: return "Pre-Idle"; break;
    case STATE_RINGING: return "Ringing"; break;
    case STATE_TALKING: return "Talking"; break;
    default: return stringformat("unknown-volumestate-%d", state);
    }
}
void VolumeManager::LoadStateVolume(int state)
{
    debug("VolumeManager: loading %hs volume\n", GetStateString(state).c_str());
    switch(state)
    {
    case STATE_IDLE: LoadIdleVolume(); break;
    case STATE_PREIDLE: /* keep talk volume */ break;
    case STATE_RINGING: LoadRingVolume(); break;
    case STATE_TALKING: LoadTalkVolume(); break;
    }
}
bool VolumeManager::SaveStateVolume(int savestate)
{
    debug("waiting for volume update\n");

    ResetEvent(m_hEvents[EV_VOLUME_UPDATE]);

    theApp.m_ipc->RequestVolume();

    while (true) {
        int n= WaitState(EVENTMASK(EV_VMGR_RING, EV_VMGR_TIMER, EV_VMGR_VOICECONNECT, 
                    EV_VMGR_RILDISCONNECT, EV_VMGR_PHONEDISCONNECT, EV_VOLUME_UPDATE));
        if (n==-1)
            return false;

        if (n==EV_VOLUME_UPDATE)
            break;

        // keep processing state updates while waiting for volume
        if (!UpdateState(n))
            return false;
    }

    debug("VolumeManager: saving %hs volume\n", GetStateString(savestate).c_str());
    switch(savestate)
    {
    case STATE_IDLE: SaveIdleVolume(); break;
    case STATE_PREIDLE: /* keep talk volume */ break;
    case STATE_RINGING: SaveRingVolume(); break;
    case STATE_TALKING: SaveTalkVolume(); break;
    }
    return true;
}
const TCHAR *REGPATH_VOLMANAGER= L"Software\\CryptoPhone\\VolumeManager";
const TCHAR *REGKEY_IDLEVOLUME= L"IdleVolume";
const TCHAR *REGKEY_RINGVOLUME= L"RingVolume";
const TCHAR *REGKEY_TALKVOLUME= L"TalkVolume";

void VolumeManager::LoadIdleVolume()
{
    DWORD volume;
    if (!GetRegistryDword(HKEY_CURRENT_USER, REGPATH_VOLMANAGER, REGKEY_IDLEVOLUME, &volume))
        volume= m_dwCurrentVolume;

    theApp.m_ipc->SetVolume(volume);
}
void VolumeManager::SaveIdleVolume()
{
    SetRegistryDword(HKEY_CURRENT_USER, REGPATH_VOLMANAGER, REGKEY_IDLEVOLUME, m_dwCurrentVolume);
}
void VolumeManager::LoadRingVolume()
{
    DWORD volume;
    if (!GetRegistryDword(HKEY_CURRENT_USER, REGPATH_VOLMANAGER, REGKEY_RINGVOLUME, &volume))
        volume= m_dwCurrentVolume;

    theApp.m_ipc->SetVolume(volume);
}

void VolumeManager::SaveRingVolume()
{
    SetRegistryDword(HKEY_CURRENT_USER, REGPATH_VOLMANAGER, REGKEY_RINGVOLUME, m_dwCurrentVolume);
}
void VolumeManager::LoadTalkVolume()
{
    DWORD volume;
    if (!GetRegistryDword(HKEY_CURRENT_USER, REGPATH_VOLMANAGER, REGKEY_TALKVOLUME, &volume))
        volume= m_dwCurrentVolume;

    theApp.m_ipc->SetVolume(volume);
}
void VolumeManager::SaveTalkVolume()
{
    SetRegistryDword(HKEY_CURRENT_USER, REGPATH_VOLMANAGER, REGKEY_TALKVOLUME, m_dwCurrentVolume);
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

// this is signalled for each ring
void VolumeManager::SignalRinging()
{
    SetEvent(m_hEvents[EV_VMGR_RING]);

    // set timer for ring-off timeout
    DisableTimer();
    EnableTimer();
}
// this is signalled once at the start of a ring sequence
void VolumeManager::SignalRingon()
{
    SetEvent(m_hEvents[EV_VMGR_RING]);
    DisableTimer();
}

void VolumeManager::SignalVoiceConnect()
{
    SetEvent(m_hEvents[EV_VMGR_VOICECONNECT]);
    DisableTimer();
}

void VolumeManager::SignalPhoneDisconnect()
{
    SetEvent(m_hEvents[EV_VMGR_PHONEDISCONNECT]);
    DisableTimer();
}
void VolumeManager::SignalRILDisconnect()
{
    SetEvent(m_hEvents[EV_VMGR_RILDISCONNECT]);
    DisableTimer();
    EnableTimer();
}

void VolumeManager::IncreaseVolume()
{
    EnterCriticalSection(&m_lock);
    m_nVolumeOffset++;
    LeaveCriticalSection(&m_lock);

    theApp.m_ipc->RequestVolume();
}
void VolumeManager::DecreaseVolume()
{
    EnterCriticalSection(&m_lock);
    m_nVolumeOffset--;
    LeaveCriticalSection(&m_lock);

    theApp.m_ipc->RequestVolume();
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

void VolumeManager::EnableTimer()
{
    m_nTimerCount = 0;

    theApp.m_dlg->SetTimer(724, 5000, staticTimerProc);
}
void VolumeManager::DisableTimer()
{
    theApp.m_dlg->KillTimer(724);
}
void CALLBACK VolumeManager::staticTimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    theApp.m_volmgr->TimerProc(hwnd, uMsg, idEvent, dwTime);
}
// timer to regularly interrupt waits
void VolumeManager::TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    SetEvent(m_hEvents[EV_VMGR_TIMER]);
}
